package discovery;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import node.NodeDescriptor;


public class DiscoveryProxy implements Discovery {
	
	protected String destinationIP;
	protected int destinationPort ;
	
	private String serverHostname;
	private int serverPort;	
	private DatagramSocket clientSocket = null;
	private  DatagramPacket sendPacketack= null;
	private boolean ackSet = false;
	
	public DiscoveryProxy(String Hostname,int serverport){
	    this.serverPort = serverport;
	    this.serverHostname = Hostname;
	}
	  
	public int addNode(NodeDescriptor local){
		String add = "DAS"; 
		Object tmp = this.query(add,local);
		if(tmp!=null)
			return Integer.parseInt(tmp.toString());
		return 1;
	}
	
	public int updateNode(NodeDescriptor local){
		String update = "DUS";
		Object tmp = this.query(update,local);
		return Integer.parseInt(tmp.toString());
	}
	
	public int delNode(NodeDescriptor local){
		String delete = "DDS";
		Object tmp = this.query(delete,local);
		return Integer.parseInt(tmp.toString());
	}
	
	public NodeDescriptor lookup(String s){
		String lookup="DLS"; 
		Object tmp = this.query(lookup,s);
	//	System.out.println(tmp.toString());
		return (NodeDescriptor) tmp;
	}
	
	
	public Object query(String method,Object payload) {
		try {			  		      			  
  	  		  ByteArrayOutputStream byteStream = new ByteArrayOutputStream();		 	
	          ObjectOutputStream out = new ObjectOutputStream(byteStream);
	          out.writeUTF(method);
	          out.flush();
	          out.close();
	          
	          byte [] sendCode = byteStream.toByteArray();
	          
			  clientSocket = new DatagramSocket(); 
	          
		      InetAddress IPAddress = InetAddress.getByName(serverHostname); 
		      System.out.println ("Attemping to connect to " + IPAddress + ") via UDP port " + String.valueOf(serverPort) );
		      
		      sendPacketack = new DatagramPacket(sendCode,sendCode.length , IPAddress, serverPort); 
		      clientSocket.send(sendPacketack); 
		      System.out.println(">> Send Code: "+method);
		      
		      int tmp [] = receiveAck();
		      NodeDescriptor descriptor=null;
		      if(tmp!=null && tmp[1]==0){
		    	  if((payload instanceof NodeDescriptor)){
		    		  sendObject(payload,tmp[0]);
		    		  tmp = receiveAck();
		    		  return tmp[1];	    	  
		    	  }
		    	  else{
		    		  sendObject(payload,tmp[0]);
		    		  descriptor = (NodeDescriptor)receiveObject();
		    		  return descriptor;
		    	  }
		    		  
			  }
		}
		catch (UnknownHostException ex) { 
	     System.err.println(ex);
	    }
	   catch (IOException ex) {
	     System.err.println(ex);
	    }
  	   finally {
		    if (!clientSocket.isClosed()) { 
		        System.out.println("Closing client soscket");
		        clientSocket.close(); 
		    } else { 
		        System.out.println("Open client socket");
		    } 
		}
		return null;
	}
		     
	public int[] receiveAck(){
	
		try{
			byte[] receiveData = new byte[1024]; 	
			DatagramPacket receiveack = new DatagramPacket(receiveData, receiveData.length); 
							 
			System.out.println ("Waiting for return packet");
			clientSocket.setSoTimeout(NodeDescriptor.getInstance().getD_timeout());		      
	 
			try{
				clientSocket.receive(receiveack);	  		
				
				int result[] = new int [2];	  		
				result[0] = receiveack.getPort();
			    
				ByteArrayInputStream receiveStream = new ByteArrayInputStream(receiveack.getData());
		  		DataInputStream dos = new DataInputStream(receiveStream);
		  		result[1] = dos.readInt();	  	
			  		if(!ackSet){
			  			ackSet=true;
			  			System.out.println("Ack Ricevuto: "+result[1]+" <<");
			  		}else
			  			System.out.println("Risultato ricevuto: "+result[1]+" <<");
			  	return result;
				}
			catch(SocketTimeoutException ste){
					System.out.println ("Timeout Occurred: Packet assumed lost");
				}
		}
		catch(Exception ex){
			ex.printStackTrace();
		}
		return null;
	}

	public void sendObject(Object local,int port){

		try {			  		      
			  ByteArrayOutputStream byteStrem=new ByteArrayOutputStream();
			  ObjectOutputStream out=new ObjectOutputStream(byteStrem);
			  out.writeObject(local);
			  out.flush();
			  out.close();
			  byte[] sendData=byteStrem.toByteArray();
						 	
			  clientSocket = new DatagramSocket(); 
	
		      InetAddress IPAddress = InetAddress.getByName(serverHostname); 
		      System.out.println ("Attemping to connect to " + IPAddress + ") via UDP port "+port);
		      
		      DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port); 
		      clientSocket.send(sendPacket); 
		      System.out.println(">> Send NodeDescriptor");	
			}
			catch (UnknownHostException ex) { 
		     System.err.println(ex);
		    }
		   catch (IOException ex) {
		     System.err.println(ex);
		    }
	}
	
	public Object receiveObject(){
		 
		try {
			clientSocket.setSoTimeout(NodeDescriptor.getInstance().getD_timeout());	
			byte[] send = new byte[1024];
			sendPacketack = new DatagramPacket(send, send.length);
			clientSocket.receive(sendPacketack); 	         
            ByteArrayInputStream InbyteStream=new ByteArrayInputStream(sendPacketack.getData());
		    ObjectInputStream ois=new ObjectInputStream(InbyteStream);
			Object tmp = ois.readObject();
			
			ois.close();
		//	System.out.println("Object "+tmp.toString());
			return tmp;
	  }
   catch (SocketTimeoutException ste){
	   System.out.println ("Timeout Occurred: Packet assumed lost");
   	  } 
   catch (IOException e) {
	   e.printStackTrace();
      } 
   catch (ClassNotFoundException e) {
	   e.printStackTrace();
      }
	//responseSocket.close(); 
	return null;
}
	
	
}

